#include "IGraph.h"

#include <cstdlib>
#include <ctime>
#include <iostream>


std::vector < std::vector <bool> > GetRandomGraph()
{
    std::vector < std::vector <bool> > graph(rand() % 1000);
    for (size_t node = 0; node < graph.size(); node++)
    {
        graph[node].resize(graph.size());
        for (int j = 0; j < graph.size(); j++)
        {
            graph[node][j] = rand() % 2;
        }
    }
    return graph;
}

template <class T>
void SimpleTests(T igraph)
{
    std::vector < std::vector <bool> > graph;
    std::vector <int> neighbours;
    graph = {{0, 1, 1}, {0, 0, 1}, {0, 0, 0}};
    igraph.construct_by_matrix(graph);
    igraph.foreach_neighbour(0, [&neighbours](int node)
                                      {
                                          neighbours.push_back(node);
                                      });
    if (neighbours.size() != 2 ||
        !(neighbours[0] == 1 && neighbours[1] == 2 || neighbours[0] == 2 && neighbours[1] == 1))
        throw std::logic_error("SimpleTests: wrong list of neighbours by " + igraph.get_name_of_method());

    neighbours.clear();
    igraph.foreach_neighbour(1, [&neighbours](int node)
                                      {
                                          neighbours.push_back(node);
                                      });
    if (neighbours.size() != 1 || neighbours[0] != 2)
        throw std::logic_error("SimpleTests: wrong list of neighbours by " + igraph.get_name_of_method());


    neighbours.clear();
    igraph.foreach_neighbour(2, [&neighbours](int node)
                                      {
                                          neighbours.push_back(node);
                                      });
    if (neighbours.size() != 0)
        throw std::logic_error("SimpleTests: wrong list of neighbours by " + igraph.get_name_of_method());

    neighbours.clear();
    graph.resize(100);
    for (int i = 0; i < 100; i++)
    {
        graph[i].resize(100);
        for (int j = 0; j < i; j++)
        {
            graph[i][j] = 1;
        }
        for (int j = i; j < graph.size(); j++)
        {
            graph[i][j] = 0;
        }
    }
    igraph.construct_by_matrix(graph);
    for (int i = 0; i < 100; i++)
    {
        neighbours.clear();
        igraph.foreach_neighbour(i, [&neighbours](int node)
                                          {
                                              neighbours.push_back(node);
                                          });
        if (neighbours.size() != i)
            throw std::logic_error("SimpleTests: wrong list of neighbours by " + igraph.get_name_of_method());
        for (int j = 0; j < i; j++)
            if (neighbours[j] != j)
                throw std::logic_error("SimpleTests: wrong list of neighbours by " + igraph.get_name_of_method());
    }
}

template <class T>
void CompleteGraphs(T igraph)
{
    std::vector < std::vector <bool> > graph;
    std::vector <int> neighbours;

    for (size_t size_of_graph = 100; size_of_graph < 5000; size_of_graph *= 1.5)
    {
        graph.resize(size_of_graph);
        for (size_t i = 0; i < size_of_graph; i++)
        {
            graph[i].resize(size_of_graph);
            for (size_t j = 0; j < size_of_graph; j++)
                graph[i][j] = 1;
            graph[i][i] = 0;
        }
        igraph.construct_by_matrix(graph);
        double average_time = 0;
        size_t quan_of_measuring = 0;
        for (size_t i = 0; i < size_of_graph; i += 100 * (size_of_graph >= 1000) + 1, quan_of_measuring++)
        {
            clock_t time_begin = clock();
            neighbours.clear();
            igraph.foreach_neighbour(i, [&neighbours](int node)
                                              {
                                                  neighbours.push_back(node);
                                              });
            average_time += (double) ((clock() - time_begin)) / CLOCKS_PER_SEC;
            if (neighbours.size() != size_of_graph - 1)
                throw std::logic_error("CompleteGraphs: wrong list of neighbours by " + igraph.get_name_of_method());
        }
        average_time /= quan_of_measuring;
        std::cerr.precision(123);
        std::cerr << "CompleteGraphs for " << igraph.get_name_of_method() << ": \n Vertices in complete graph: " << size_of_graph << "\n"
                  << " Average time of collecting all neighbours: " << average_time << "\n"
                  << " Memory (bytes): " << igraph.byte_size() << "\n" << std::endl;
    }
}

template <class T>
void AcyclicGraphs(T igraph)
{
    std::vector < std::vector <int> > graph;
    std::vector <int> neighbours;
    size_t min_size, max_size;
    if (igraph.get_name_of_method() == "list graph")
    {
        min_size = 10000;
        max_size = 10000000;
    }
    if (igraph.get_name_of_method() == "matrix graph")
    {
        min_size = 100;
        max_size = 7000;
    }
    for (size_t size_of_graph = min_size; size_of_graph < max_size; size_of_graph *= 2)
    {
        graph.resize(size_of_graph);
        for (size_t i = 0; i < size_of_graph - 1; i++)
        {
            graph[i].resize(1);
            graph[i][0] = i + 1;
        }
        igraph.construct_by_list(graph);
        double full_time = 0;
        for (size_t i = 0; i < size_of_graph; i++)
        {
            clock_t time_begin = clock();
            neighbours.clear();
            igraph.foreach_neighbour(i, [&neighbours](int node)
                                              {
                                                  neighbours.push_back(node);
                                              });
            full_time += (double) ((clock() - time_begin)) / CLOCKS_PER_SEC;
        }
        std::cerr.precision(123);
        std::cerr << "AcyclicGraphs for " << igraph.get_name_of_method() << ": \n Vertices in acyclic graph: " << size_of_graph << "\n"
                  << " Full time of collecting all neighbours: " << full_time << "\n"
                  << " Memory (bytes): " << igraph.byte_size() << "\n" << std::endl;
    }
}

template <class T>
void RandomGraphs(T igraph)
{
    std::vector < std::vector <bool> > graph;
    std::vector <int> neighbours;

    for (size_t quantity_of_tests = 10 + rand() % 100; quantity_of_tests > 0; quantity_of_tests--)
    {
        graph = GetRandomGraph();
        igraph.construct_by_matrix(graph);
        double full_time = 0;
        for (size_t i = 0; i < graph.size(); i++)
        {
            neighbours.clear();
            clock_t time_begin = clock();
            igraph.foreach_neighbour(i, [&neighbours](int node)
                                         {
                                             neighbours.push_back(node);
                                         });
            full_time += ((double) (clock() - time_begin)) / CLOCKS_PER_SEC;
            for (size_t j = 0; j < neighbours.size(); j++)
                if (!graph[i][neighbours[j]])
                    throw std::logic_error("RandomGraphs: wrong list of neighbours by " + igraph.get_name_of_method());
            for (size_t j = 0; j < graph.size(); j++)
                if (graph[i][j] && std::find(neighbours.begin(), neighbours.end(), j) == neighbours.end())
                    throw std::logic_error("RandomGraphs: wrong list of neighbours by " + igraph.get_name_of_method());
        }
        std::cerr.precision(123);
        std::cerr << "RandomGraphs for " << igraph.get_name_of_method() << ":\n Vertices in random graph: " << igraph.number_of_vertices() << "\n"
                  << " Full time of collecting all neighbours: " << full_time << "\n"
                  << " Memory (bytes): " << igraph.byte_size() << "\n" << std::endl;
    }
}

void TestGraph()
{
    srand(20000007);
    ListGraph list_graph;
    MatrixGraph matrix_graph;

    SimpleTests(matrix_graph);
    std::cerr << "SimpleTests for MatrixGraph: OK" << std::endl;
    CompleteGraphs(matrix_graph);
    std::cerr << "CompleteGraphs for MatrixGraph: OK" << std::endl;
    AcyclicGraphs(matrix_graph);
    std::cerr << "AcyclicGraphs for MatrixGraph: OK" << std::endl;
    RandomGraphs(matrix_graph);
    std::cerr << "RandomGraphs for MatrixGraph: OK" << std::endl;


    SimpleTests(list_graph);
    std::cerr << "SimpleTests for ListGraph: OK" << std::endl;
    CompleteGraphs(list_graph);
    std::cerr << "CompleteGraphs for ListGraph: OK" << std::endl;
    AcyclicGraphs(list_graph);
    std::cerr << "AcyclicGraphs for ListGraph: OK" << std::endl;
    RandomGraphs(list_graph);
    std::cerr << "RandomGraphs for ListGraph: OK" << std::endl;
}
